最近剛好有在看 Clean Architecture 這本書,裡面有提到大名鼎鼎的 SOLID 原則,雖然還沒有完全看完,但今天可以先來聊聊這五大原則。
這是由 Robert C. Martin 所提出的物件導向設計中的五大原則,目的在於使設計的系統更易於維護和擴展。
SOLID 分別是由以下五個原則所組成:
A class should have one and only one reason to change, meaning that a class should have only one job
每個 class 只專注負責一個功能,並且這個功能要完全封裝在這個 class 中。簡而言之,類別應該只有一個修改它的理由。
一個修改他的理由伴隨著的都是使用者,因此我們也可以說一個模組應該只對唯一一個角色負責,必須分開不同角色所依賴的程式碼。
優點
class Account:
def __init__(self, name):
self.name = name
self.balance = 0
def deposit(self, amount):
self.balance += amount
return f"Deposited {amount}, new balance: {self.balance}"
def withdraw(self, amount):
if amount > self.balance:
return "Insufficient funds"
self.balance -= amount
return f"Withdrawn {amount}, new balance: {self.balance}"
def change_name(self, name):
self.name = name
上述 class 違反 SRP:
存錢與領錢:與帳戶餘額有關。
改名:與帳戶資料管理有關。
兩者對應的角色不同,其依賴的方法需要分開。
Objects or entities should be open for extension but closed for modification.
類別應該對擴展開放,但對修改封閉。簡而言之,就是可以通過擴展類別(例如繼承)來增加新功能,而不是去修改現有的類別。
優點:
class Animal:
@classmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
class AnimalSoundPrinter:
def print_sound(self, animal):
print(animal.speak())
dog = Dog()
cat = Cat()
printer = AnimalSoundPrinter()
printer.print_sound(dog)
printer.print_sound(cat)
若要得知動物的聲音,不需要修改 AnimalSoundPrinter,只需要再新增一種動物的類別即可。
Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T
子型別的物件應該要能夠替代父型別的物件,且不影響程式的正確性
優點:
class Person:
first_name: str
last_name: str
def display_name(self):
print(self.last_name + self.first_name)
class Employee(Person):
first_name: int
last_name: int
def display_name(self):
print(self.first_name + self.last_name)
這裡有兩個地方違反 LSP,
A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.
不應強迫使用者依賴他們不需要或不使用的介面。換言之,應將龐大的介面拆解為更小、更專門的介面,以滿足不同的使用者的需求。
書中有個例子我覺得很好解釋: 若包包中攜帶了你不需要的東西,你卻要依賴這個包包來做事,可能會導致意想不到的麻煩。
優點:
class Animal:
def fly(self):
return "fly"
def run(self):
return "run"
class Bird(Animal):
def fly(self):
return "fly"
不是每隻動物都會跑會飛,應該讓會飛的 Bird 去實現 fly。
Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.
高階模組不應該依賴低階模組,兩者都應該依賴抽象介面,原始碼的依賴關係只涉及抽象不涉及具體。
優點:
class Vehicle:
def brake(self):
return "brake!"
class Bus:
def __init__(self):
self.bus = Vehicle()
def stop_bus(self):
return self.bus.brake()
bus = Bus()
print(bus.stop_bus()) // "brake"
Bus class 依賴 Car 的 brake,如果之後 Car 的 brake 修改了,就會導致 Bus brake 出錯。
因此可以使用 python 的 abstractmethod
from abc import ABC, abstractmethod
class Vehicle(ABC):
@abstractmethod
def brake(self):
pass
class Bicycle(Vehicle):
def brake(self):
return "Bicycle braking!"
class Car(Vehicle):
def brake(self):
return "Car braking!"
class VehicleManager:
def __init__(self, vehicle: Vehicle):
self.vehicle = vehicle
def brake(self):
return self.vehicle.brake()
bicycle = Bicycle()
car = Car()
transport_manager_for_bicycle = VehicleManager(bicycle)
print(transport_manager_for_bicycle.brake())
transport_manager_for_car = VehicleManager(car)
print(transport_manager_for_car.brake())
可以看到低階模組(bicycle, car) 高階模組(vehicle) 都依賴抽象介面,再透過低階模組的實例化,來執行不同的剎車。
以上就是 SOLID 的介紹,我們明天見!
Refence: